#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "fingerdb.h"



/**************************************************
*                                                 *
*   This file contents functions, which provide   *
*          content storage abstraction.           *
*                                                 *
**************************************************/

#define FINGERDB_PATH_MAXLEN 512
#define FINGERDB_DB_DIR "/var/db/upekux"

#define FINGERDB_DIR_MASK  0700
#define FINGERDB_FILE_MASK 0600

/******************************
fun fingerdbCreateFinger
	create new record in db
return
	negative on error
	id of new record on succes
*******************************/
int fingerdbCreateFinger( void ){
	
	int res;
	int i;
	char path[FINGERDB_PATH_MAXLEN];
	struct stat dirstat;
	
	for( i = 1; ;i++ ){
		sprintf( path, "%s/%i", FINGERDB_DB_DIR, i );
		if( stat(path, &dirstat) ){
			if( errno == ENOENT ) break;
			perror( "fingerdbCreateFinger stat" );
			return -1;
		}
	}
	
	res = mkdir( path, FINGERDB_DIR_MASK );
	if( res ) perror( "fingerdbCreateFinger mkdir" );
	
	return res < 0 ? res : i;
}

/***********************************
func fingerdbCheckIdExist
	check exist id record in db
input
	id which need check
return
	1 if exist
	0 if not exist
***********************************/
int fingerdbCheckIdExist( int id ){
	
	struct stat dirstat;
	char path[FINGERDB_PATH_MAXLEN];
	
	sprintf( path, "%s/%i", FINGERDB_DB_DIR, id );
	
	if( !stat(path, &dirstat) )return 1;
	return 0;
}

/*************************************
fun fingerdbSetField
	set new field value in db
input
	id - id of record in db
	field - field id the record
	data - new content of field
	datalen - length in bytes of data
return
	0 on success
	negative on error
**************************************/
int fingerdbSetField( unsigned int id, const char* field, const void* data, int datalen ){
	
	int wres;
	int writelen = 0;
	int fd;
	char path[FINGERDB_PATH_MAXLEN];
	
	//Prevent use slashes in field name
	for( int i = 0; field[i]; i++ ) if( field[i] == '/' || field[i] == '\\' ){
		printf( "WARNING: attempt to use '%c' in field name!\n", field[i] );
		return -EINVAL;
	}
	
	sprintf( path, "%s/%u/%s", FINGERDB_DB_DIR, id, field );
	fd = open( path, O_CREAT | O_WRONLY | O_TRUNC, FINGERDB_FILE_MASK );
	if( fd < 0 ){
		perror( "fingerdbSetField open" );
		return -1;
	}
	
	do{
		wres = write( fd, data + writelen, datalen - writelen );
		if( wres < 0 ){
			perror( "fingerdbSetField write" );
			goto l_close;
		}
		writelen += wres;
	}while( writelen < datalen );
	
l_close:
	close( fd );
	return wres < 0 ? wres : 0;
}

/**************************************************************
func fingerdbGetField
	read content of field
input
	id - if of record in db
	field - name of field in record
	buf - pointer to buffer, in which the data will be placed
	buflen - size of buf
return
	negative on error
	0 if field not exist or empty
	positive - amount of data read
***************************************************************/
int fingerdbGetField( unsigned int id, const char* field, void* buf, int buflen ){
	
	int filesize;
	int readlen;
	int datalen;
	int needRead;
	char path[FINGERDB_PATH_MAXLEN];
	int fd;
	
	datalen = 0;
	
	if( buflen < 1 ){
		printf( "%s: output buffer too small\n", __FUNCTION__ );
		return -ENOMEM;
	}
	
	//Prevent use slashes in field name
	for( int i = 0; field[i]; i++ ) if( field[i] == '/' || field[i] == '\\' ){
		printf( "WARNING: attempt to use '%c' in field name!\n", field[i] );
		return -EINVAL;
	}
	
	sprintf( path, "%s/%u/%s", FINGERDB_DB_DIR, id, field );
	fd = open( path, O_RDONLY );
	if( fd < 0 ){
		if( errno == ENOENT ) return 0;//not error
		perror( "fingerdbGetField open" );
		return fd;
	}
	
	filesize = lseek( fd, 0, SEEK_END );
	if( filesize < 0 ){
		perror( "fingerdbGetField lseek" );
		goto l_close;
	}
	if( !filesize ) goto l_close;
	lseek( fd, 0, SEEK_SET );
	
	needRead = filesize >= buflen ? buflen : filesize;
	
	do{
		readlen = read( fd, buf + datalen, needRead - datalen );
		if( readlen < 0 ){
			perror( "fingerdbGetField read" );
			goto l_close;
		}
		datalen += readlen;
	}while( datalen < needRead );
	
	if( datalen < buflen ) memcpy( buf + datalen, "\0", 1 );
	
l_close:
	close( fd );
	return datalen;
}
/**********************************************
func fingerdbRemoveFinger
	trash all data in record and delete record
input
	id - id of record, wich be removed
return
	0 on success
	not 0 on error
***********************************************/
int fingerdbRemoveFinger( unsigned int id ){
	
	int res = 0;
	char path[FINGERDB_PATH_MAXLEN];
	DIR* fingerdir;
	struct dirent* diritem;
	
	sprintf( path, "%s/%u", FINGERDB_DB_DIR, id );
	fingerdir = opendir( path );
	if( !fingerdir ){
		perror( "fingerdbRemoveFinger opendir" );
		return -ENOENT;
	}
	
l_read_next_diritem:
	diritem = readdir( fingerdir );
	if( diritem ){
		
		if( !strcmp(".", diritem->d_name) || !strcmp("..", diritem->d_name) ) goto l_read_next_diritem;
		
		sprintf( path, "%s/%u/%s", FINGERDB_DB_DIR, id, diritem->d_name );
		res = remove( path );
		if( res ) perror( "fingerdbRemoveFinger remove file" );
		else goto l_read_next_diritem;
	}
	
	closedir( fingerdir );
	
	if( !res ){
		
		sprintf( path, "%s/%u", FINGERDB_DB_DIR, id );
		res = remove( path );
		if( res ) perror( "fingerdbRemoveFinger remove dir" );
	}
	
	return res;
}

/**********************************
func fingerdbOpen
	open db for operate
return
	pointer to db
	NULL on error
**********************************/
void* fingerdbOpen( void ){
	return opendir( FINGERDB_DB_DIR );
}

/**********************************
func fingerdbSerachNextId
	search next record id in the db
input
	pointer to the db
return
	0 - error or not found
	not 0 - id
**********************************/
int fingerdbSearchNextId( void* dbp ){
	
	int id;
	struct dirent* de;
	
l_read:
	de = readdir( dbp );
	if( !de ) return 0;
	
	if( sscanf(de->d_name, "%i", &id) == 1 ) return id;
	
	goto l_read;
}

/**********************************
func fingerdbClose
	close db
input
	pointer to the db to be closed
**********************************/
void fingerdbClose( void* dbp ){
	closedir( dbp );
}
